🧠 AI 的概率魔法

Logits · Softmax · Temperature —— 模型脑子里的「裸分 · 分蛋糕 · 胆量旋钮」
📐 通俗科普 ⚡ 含 Mermaid 图解 🐍 附 Python 源码

第〇章 · 从大模型架构看 Logits 在哪里产生 架构

要真正理解 Logits、Softmax 和 Temperature,先得知道 它们在大模型里到底住在哪一层。这一章从大模型(以 GPT、LLaMA、Qwen 等为代表)的 整体架构 出发,定位 Logits 的"出生地"。

🏗️ 大模型的三层拆解

大语言模型(LLM)的本质是 N 层 Transformer Block 的堆叠。每一层 Block 都包含两个核心子模块:多头注意力(MHA) 和 前馈网络(FFN),再加上残差连接和 LayerNorm。

flowchart TD A["用户输入(一句话)"] --> B["Token 嵌入层(词表→向量)"] B --> C["Transformer Block × N 层"] C --> D["输出层 LM Head(线性映射)"] D --> E["Logits(词表大小维)"] E --> F["Softmax → 概率分布"] F --> G["采样 → 输出 Token"] H["每个 Block 内部:MHA + FFN"] -.-> C
图〇-1 · 大模型整体流程:Logits 在最后一层产生

🧩 单个 Transformer Block 长什么样?

每个 Block 由两部分组成:

  1. 多头注意力(MHA):让每个 Token "看到"上下文中其他 Token 的信息,本质是 Q/K/V 三个投影矩阵 + 注意力计算。Qwen3.8-27B 用 64 头,每头 128 维,合计 268M 参数。
  2. 前馈网络(FFN):对注意力输出做非线性变换,现代模型多用 SwiGLU 变体(上投影 + 门控 + 下投影三个矩阵)。Qwen3.8-27B 每层 FFN 约 805M 参数。
flowchart TD In["输入(如 8192 维)"] --> LN1["LayerNorm"] LN1 --> MHA["多头注意力 MHA"] MHA --> Add1["残差 +"] Add1 --> LN2["LayerNorm"] LN2 --> FFN["前馈网络 FFN(SwiGLU)"] FFN --> Add2["残差 +"] Add2 --> Out["输出(如 8192 维)"] MHA -.-> QKV["Q / K / V 投影"] FFN -.-> UpGate["Up + Gate + Down 投影"]
图〇-2 · 单个 Transformer Block 内部结构

🎯 Logits 在哪里诞生?

经过 N 层 Transformer Block 之后,模型得到一个 高维隐藏向量(如 4096 维或 8192 维)。这个向量浓缩了"当前语境下,下一个 Token 应该长什么样"的全部信息。

最后一步:LM Head(输出层)将这个向量通过一个 线性映射(无激活函数),投影到 词表大小 的维度(如 50000 或 128000),得到的就是 Logits。

🐍 PyTorch 风格的 Logits 生成
# 假设 hidden_size=4096, vocab_size=50000
lm_head = nn.Linear(4096, 50000, bias=False)

# hidden 是 Transformer 最后一层的输出 (batch, seq, 4096)
logits = lm_head(hidden)   # → (batch, seq, 50000)

# 然后除以 Temperature,过 Softmax,采样得到下一个 Token
logits = logits / temperature
probs  = softmax(logits)
next_token = sample(probs)
                
🔑 关键结论: Logits 不是"凭空产生"的 —— 它是 Transformer 最后一层的隐藏向量与词表中每一个 Token 做点积(相似度计算)的结果。哪个 Token 与当前语境最像,它的 Logit 分就最高。接下来才是 Softmax 把这些分数变成概率,Temperature 控制"胆量"。

📊 各组件参数量一览(以 Qwen3.8-27B 为例)

总结这一章:大模型 = N 层 Transformer Block 堆叠 → 最后一层的隐藏向量过一个线性层 → 得到 Logits → 除以 Temperature → Softmax → 概率 → 采样。Logits 是整个流程的"第一站",后续的 Softmax 和 Temperature 都是对它的加工。

第一章 · 为什么 AI 不直接算概率? 困境

我们总说 AI 输出「80% 是猫,20% 是狗」—— 但 在模型内部,它从来不会直接算概率。因为概率必须乖乖待在 0 到 1 之间,而神经网络天生是个「暴躁的算术家」,内部全是矩阵乘法,动辄算出 几千 或 负数。

如果强行让每一层神经元都「守规矩」,结果必须卡在 0~1 之间,那就像让短跑运动员裹着棉被跑步 —— 又慢又使不出全力(梯度消失,模型学不动)。

💡 结论: 模型需要「言论自由」—— 先随便算,算出什么数都行,最后再统一「翻译」成概率。这个随便算出来的「原始裸分」,就叫 Logits。
flowchart TD A[输入数据] --> B[神经网络
自由计算] B --> C[Logits 裸分
(任意实数)] C --> D[Temperature 调节
(缩放差距)] D --> E[Softmax 归一化
(变成概率)] E --> F[概率分布
(总和等于1)] F --> G[采样抽签
(随机选择)] G --> H[输出 Token
(最终答案)]
图1 · 完整的计算流水线(从上到下)

🤔 那到底什么是「梯度消失」?

简单说:模型靠「误差反向传播」来学习,如果中间某一步的导数(变化率)接近 0,那前面的层就收不到有效的学习信号,相当于 教了半天,前排学生全睡着了。而 Logits 是线性运算,它的导数是常数 1,信号可以畅通无阻地传回每一层。

🐍 直觉对比 · 为什么 Logits 更友好
# 假设我们硬把输出限制在 [0,1]
def clamped_forward(x):
    return max(0, min(1, x))  # 强行截断

# 当 x > 1 或 x < 0 时,导数为 0 → 梯度消失!
# 而 Logits 直接输出 x,导数为 1 → 学习信号满格 💪
                

第二章 · Logits —— 模型的「裸分」 自由

Logits 就是神经网络在最后一层算出来的 原始分数,它可以是 正数、负数、大数 或 小数,完全没有限制。模型在 Logits 空间里 随便撒欢,想打多少分就打多少分。

在二分类场景下,Logits 确实来源于 「对数几率」(Log Odds):如果某件事发生的概率是 p,那么胜算比 Odds = p/(1-p),取自然对数后就是 ln(p/(1-p)),范围从 负无穷 到 正无穷。这正好把概率空间「展开」成了整个实数轴,模型可以自由翱翔。

🎯 通俗比喻: Logits 就像选秀节目中导师打的「原始分」—— 可以给 +999,也可以给 −999,怎么爽怎么来。最后再统一换算成百分比。
flowchart TD A["事件概率 p(0 到 1)"] --> B["Odds = p/(1-p)(0 到 正无穷)"] B --> C["Logits = ln(Odds)(负无穷 到 正无穷)"] C --> D["模型自由计算,任意实数都允许"] D --> E["最后用 Softmax 翻译回概率"]
图2 · 从概率到 Logits 的「展开」过程

🧪 一个具体的例子

假设猫的概率 p = 0.8,那么 Odds = 0.8/0.2 = 4,Logits = ln(4) ≈ 1.386。如果 p = 0.99,Odds = 99,Logits ≈ 4.595。可见,越接近 1,Logits 就越大,模型就越「自信」。反过来 p = 0.1,Logits ≈ −2.197,表示模型觉得这事儿不靠谱。

🐍 从概率到 Logits 的转换
import math

def prob_to_logits(p):
    # 将概率 p 转换为 Logits(对数几率)
    if p <= 0 or p >= 1:
        raise ValueError("p 必须在 (0,1) 之间")
    return math.log(p / (1 - p))

print(prob_to_logits(0.8))   # 输出 ≈ 1.386
print(prob_to_logits(0.99))  # 输出 ≈ 4.595
print(prob_to_logits(0.1))   # 输出 ≈ −2.197
                

第三章 · Softmax —— 「分蛋糕机器」 归一化

模型打了一堆 Logits 裸分,比如针对「猫 / 狗 / 车」打出了 [2.0, 1.0, 0.0]。怎么把这些乱七八糟的分数变成 加起来刚好等于 100% 的靠谱概率? Softmax 就是那个「操盘手」,它分三步走:

  1. 取指数 e^x:把负数变正数,且永远 > 0。2 → 7.39,1 → 2.72,0 → 1.00。
  2. 求和:7.39 + 2.72 + 1.00 = 11.11。
  3. 归一化:每个指数除以总和,得到概率。7.39/11.11 ≈ 66.5%,2.72/11.11 ≈ 24.5%,1.00/11.11 ≈ 9%。
🍰 分蛋糕比喻: Logits 是每个人「喊出的胃口」,取指数后变成「实际饭量」,然后 Softmax 把整块蛋糕按饭量比例分给大家,分完正好 100%。
flowchart TD A[Logits: 2, 1, 0] --> B[取指数: e^2, e^1, e^0] B --> C[求和: 7.39 + 2.72 + 1.00 = 11.11] C --> D[归一化: 各除以 11.11] D --> E[概率: 66.5%, 24.5%, 9.0%] E --> F[总和等于100% 且每个大于0]
图3 · Softmax 把裸分变成概率分布

🔍 为什么偏偏用「e 的指数」?

因为指数函数 e^x 有两个绝佳特性:

🐍 Softmax 的 Python 实现
import numpy as np

def softmax(logits):
    # 输入: logits 列表或数组
    # 输出: 概率分布,总和为 1
    exp_vals = np.exp(logits)               # 取指数
    sum_exp = np.sum(exp_vals)              # 求和
    return exp_vals / sum_exp               # 归一化

logits = [2.0, 1.0, 0.0]
probs = softmax(logits)
print(probs)   # 输出: [0.665, 0.245, 0.090]
print(sum(probs)) # 输出: 1.0
                

第四章 · Temperature —— 「胆量调节旋钮」 创造

在把 Logits 扔进 Softmax 之前,我们可以先除以一个 温度 T。这个 T 就像 AI 的 「胆量调节旋钮」,直接控制着模型是「死磕最优解」还是「天马行空」。

🎛️ 旋钮比喻: T 调低,AI 胆子变小,只敢说最稳妥的话;T 调高,AI 胆子变大,敢尝试各种新鲜词汇,创造力爆棚(但也可能胡言乱语)。
flowchart TD A["Logits: 10, 9, 8"] --> B["除以 T"] B --> C{"T 小于 1(低温)"} B --> D{"T = 1(常温)"} B --> E{"T 大于 1(高温)"} C --> F["概率尖锐,第一名大于99%"] D --> G["概率正常,按原始差距"] E --> H["概率平坦,各选项接近"]
图4 · 不同温度下的概率分布形态

📊 直观感受:T 从 0.1 到 10 的变化

假设 Logits = [10, 9, 8]:

🐍 带 Temperature 的 Softmax
import numpy as np

def softmax_with_temp(logits, temperature):
    scaled = np.array(logits) / temperature
    exp_vals = np.exp(scaled)
    return exp_vals / np.sum(exp_vals)

logits = [10, 9, 8]
print(softmax_with_temp(logits, 0.5))   # 尖锐
print(softmax_with_temp(logits, 1.0))   # 正常
print(softmax_with_temp(logits, 10.0))  # 平坦
                

第五章 · 采样 —— 「抽签转盘」 随机

Softmax 算出的只是概率分布,比如「猫 60%,狗 40%」。但模型 并不总是直接选概率最大的(那叫「贪心解码」)。实际中,模型会把这个概率分布做成一个 「抽签转盘」,然后随机拨动指针。

这就是为什么问同一个问题两次,答案可能不一样! 因为转盘每次停的位置可能不同。Temperature 越高,转盘上各扇区面积越接近,抽中冷门选项的概率越大;Temperature 越低,最大扇区几乎占满整个转盘,结果就变得死板且确定。

🎰 抽签比喻: 概率是「转盘上扇区的大小」,采样就是「拨动指针,看它停在哪儿」。随机性 = 创造力,但也要控制温度别太放飞。
flowchart TD A[概率分布
猫 60%, 狗 40%] --> B[构建累积分布
猫 0到0.6, 狗 0.6到1.0] B --> C[生成随机数
r 在 0 到 1 之间] C --> D{随机数落在哪个区间?} D -->|r 小于 0.6| E[选择「猫」] D -->|r 大于等于 0.6| F[选择「狗」]
图5 · 根据概率分布进行随机采样

🧪 采样的工程实现

实际代码中,我们使用 多项式分布(Multinomial Distribution)或 torch.multinomial 来实现采样。核心逻辑就是:按照概率大小,给每个选项分配一个「区间」,然后扔一个 0~1 的随机数,看落在哪个区间里。

🐍 从概率分布中采样
import numpy as np

def sample_from_probs(probs):
    cumsum = np.cumsum(probs)           # 累积分布 [0.6, 1.0]
    r = np.random.rand()                # 生成 0~1 随机数
    for i, threshold in enumerate(cumsum):
        if r < threshold:
            return i                    # 返回选中的索引
    return len(probs) - 1

probs = [0.6, 0.4]   # 猫 60%, 狗 40%
print(sample_from_probs(probs))  # 可能是 0(猫)或 1(狗)
                

第六章 · Transformer 中的 Logits 生成 幕后

在 Transformer 架构(比如 GPT 系列)里,Logits 是怎么算出来的?其实最后一步非常简单:

  1. 编码:输入一句话,经过几十层 Transformer 解码器后,得到一个 高维向量(比如 4096 维)。
  2. 线性映射:这个向量通过一个 全连接层(没有激活函数),被映射到 词表大小 的维度(比如 50000 维)。
  3. 输出 Logits:映射的结果就是 Logits,形状为 [batch, 序列长度, 词表大小]。

可以理解为:模型把「当前语境」浓缩成一个「灵魂向量」,然后去跟词表里的每一个单词「做点积」(算相似度),得到的相似度分数就是 Logits。哪个词跟当前语境最像,它的 Logits 分就最高。

🔧 工程比喻: 销售(模型)把客户需求(输入)整理成一张「需求清单」(向量),然后去仓库(词表)里跟 5 万件商品逐一比对,每件商品打一个「匹配分」—— 这一堆匹配分就是 Logits。
flowchart TD A["输入文本(如 今天天气)"] --> B["Transformer 解码器(几十层)"] B --> C["隐藏向量(如 4096 维)"] C --> D["线性映射层(无激活函数)"] D --> E["词表打分(如 50000 维)"] E --> F["Logits 输出(每个词的原始分数)"]
图6 · Transformer 最后一层生成 Logits 的过程

🧩 代码层面:PyTorch 风格的伪代码

在 PyTorch 中,这通常就是 nn.Linear(hidden_size, vocab_size) 一层搞定。不需要任何激活函数,直接输出原始分数。

🐍 PyTorch 风格的 Logits 生成
import torch
import torch.nn as nn

linear_layer = nn.Linear(4096, 50000, bias=True)

hidden = torch.randn(1, 1, 4096)   # 模拟 Transformer 输出
logits = linear_layer(hidden)           # 形状: (1, 1, 50000)

print(logits.shape)  # torch.Size([1, 1, 50000])
                

🎯 总结 · 一条流水线,三个关键词

Logits 是模型的「原始裸分」—— 任意实数,自由奔放。
Softmax 是「分蛋糕机器」—— 把裸分变成概率,总和 1,每个 > 0。
Temperature 是「胆量旋钮」—— 调低变保守,调高变创造。

flowchart TD A[模型打分
Logits 裸分] --> B[调节差距
除以 Temperature] B --> C[归一化
Softmax] C --> D[概率分布] D --> E[采样抽签] E --> F[输出 Token]
最终 · 完整流程再回顾

记住这句话: 概率是给人类看的,Logits 才是模型真正「爱」的。而 Temperature 和采样,则决定了这个模型是 「严肃的专家」 还是 「天马行空的诗人」。

🚀 最后的小彩蛋: 下次问 AI 同一个问题两次得到不同答案,别觉得它「精分」—— 它只是按照给出的 Temperature,在转盘上重新抽了一次签而已!
华杉的技术博客 · 苏ICP备2026063680号-1
首页